import { REACT_ELEMENT_TYPE, REACT_FRAGMENT_TYPE } from 'shared/ReactSymbols';
import { Key, Props, ReactElementType } from 'shared/ReactTypes';
import {
	createFiberFromElement,
	createFiberFromFragment,
	createWorkInProgress,
	FiberNode
} from './fiber';
import { ChildDeletion, Placement } from './fiberFlags';
import { Fragment, HostText } from './workTags';

type ExistingChildren = Map<string | number, FiberNode>;

// shouldTrackEffects是否追踪副作用
function ChildReconciler(shouldTrackEffects: boolean) {
	//向父fiber 的deletions 中添加不能复用 需要删除的子fiber
	// 同时给returnFiber 添加 删除 的flags
	function deleteChild(returnFiber: FiberNode, childToDelete: FiberNode) {
		if (!shouldTrackEffects) {
			return;
		}
		const deletions = returnFiber.deletions;
		if (deletions === null) {
			returnFiber.deletions = [childToDelete];
			returnFiber.flags |= ChildDeletion;
		} else {
			// 之前已经标记过 ChildDeletion的 flags 不用再标记
			deletions.push(childToDelete);
		}
	}

	function deleteRemainingChildren(
		returnFiber: FiberNode,
		currentFirstChild: FiberNode | null
	) {
		if (!shouldTrackEffects) {
			return;
		}
		let childToDelete = currentFirstChild;
		// 从当前currentFirstChild 节点出发删除 后面兄弟节点
		while (childToDelete !== null) {
			deleteChild(returnFiber, childToDelete);
			childToDelete = childToDelete.sibling;
		}
	}

	// 这个函数的作用是：beginwork 阶段 处理单个 元素 时 根据 update 和 mount 阶段 返回 子 fiber
	// mount 阶段直接创建新fiber返回
	// update 阶段 根据 type 和 key 来判断是否可复用fiber
	// 可能 之前是 多个 元素 变成了单个

	function reconcileSingleElement(
		returnFiber: FiberNode,
		currentFiber: FiberNode | null,
		element: ReactElementType
	) {
		const key = element.key;

		// 更新阶段 单节点diff 过程
		// 1.key相同 type相同 标记删除其他老的fiber节点 然后复用或新建一个并返回Fiber  新建表示 第一次更新
		// 2.key相同 type不同 将之前老的都标记删除 然后新建一个Fiber 返回
		// 3.key 不同 标记删除对应的老fiber 然后 对比 他的兄弟节点 重复 1 2 没找到的话 就新建一个fiber 返回
		// 更新的情况
		// 可能 之前是 多个 元素 变成了单个
		while (currentFiber !== null) {
			// update
			if (currentFiber.key === key) {
				// key相同
				if (element.$$typeof === REACT_ELEMENT_TYPE) {
					if (currentFiber.type === element.type) {
						let props = element.props;
						if (element.type === REACT_FRAGMENT_TYPE) {
							// 如果是一个fragment 的话 props 为他的 children
							props = element.props.children;
						}
						// type相同
						// 取缓冲区的 fiber 不是新建一个fiber
						// useFiber
						// currentFiber 这个 fiber 是 当前工作区的fiber
						//第一次更新 如果 可以复用 那就 拿到当前 currentFiber 的 alternate 没有 就创建一个 然后链接上
						// 第二次更新 复用 拿到currentFiber.alternate  然后 重置一些属性 和 复用 currentFiber上的属性 然后返回
						// 后面更新 可以互用的 话 就是 alternate 来回切换 不会创建新的了
						// 复用了 Fiber 也就 复用了 dom
						const existing = useFiber(currentFiber, props);
						existing.return = returnFiber;
						// 当前节点可复用，标记剩下的兄弟节点删除
						deleteRemainingChildren(returnFiber, currentFiber.sibling);
						return existing; // 直接返回 找到可以复用的了 直接返回
					}

					// key相同，type不同 删掉所有旧的
					deleteRemainingChildren(returnFiber, currentFiber);
					break; // 跳出 重新创建
				} else {
					if (true) {
						console.warn('还未实现的react类型', element);
						break;
					}
				}
			} else {
				// key不同，删掉旧的
				deleteChild(returnFiber, currentFiber);
				// 去判断 之前节点的兄弟节点是否可以复用
				currentFiber = currentFiber.sibling;
			}
		}

		// 初次渲染 或者 将 旧fiber加入到删除数组种之后 重新创建
		// currentFiber为null 表示 普通节点的首次渲染 直接 new 一个fiber返回
		// 根据element创建fiber
		let fiber;
		if (element.type === REACT_FRAGMENT_TYPE) {
			// fragment 类型 的react 元素 props是存在children属性上的
			// element = jsxs(Fragment, props:{
			//   children: [
			//       jsx("div", {}),
			//       jsx("div", {})
			//   ]
			// });
			fiber = createFiberFromFragment(element.props.children, key);
		} else {
			fiber = createFiberFromElement(element);
		}
		fiber.return = returnFiber;
		return fiber;
	}

	// 这个函数的作用是：beginwork 阶段 处理单个 文本节点 时 根据 update 和 mount 阶段 返回 子 fiber
	// 可能之前是多个 元素 变成了 单个文本
	function reconcileSingleTextNode(
		returnFiber: FiberNode,
		currentFiber: FiberNode | null,
		content: string | number
	) {
		// 更新的情况
		// 可能之前是多个 元素 变成了 单个文本
		while (currentFiber !== null) {
			// update
			// currentFiber 是 文本节点
			if (currentFiber.tag === HostText) {
				// 文本节点 类型没变，可以复用
				const existing = useFiber(currentFiber, { content });
				existing.return = returnFiber;
				// 当前节点可复用，标记剩下的兄弟节点删除
				deleteRemainingChildren(returnFiber, currentFiber.sibling);
				return existing;
			}
			// 类型变了的话
			// 比如 之前是 div 现在是 哈哈哈 从 div 变成了 文本节点
			// 所以需要删除之前的 fiber 节点
			deleteChild(returnFiber, currentFiber);

			// 继续判断后面的 节点
			currentFiber = currentFiber.sibling;
		}
		// 删除 之后 进入创建新的 文本节点

		// 首次渲染
		const fiber = new FiberNode(HostText, { content }, null);
		fiber.return = returnFiber;
		return fiber;
	}

	// 这个函数作用是 标记flags
	function placeSingleChild(fiber: FiberNode) {
		if (shouldTrackEffects && fiber.alternate === null) {
			// hostRootFiber 的alternate 不为null
			// 闭包里面需要追踪 插入 副作用
			// 当 fiber 是 hostRootFiber.child 的时候  即 app节点 此时 fiber.alternate 为null
			// 所以 此时 需要标记 插入 因为需要将首次渲染 的 离屏dom 树 插入到容器Container中
			// 因为 更新的话 移动 插入这些 在其他地方 标记了
			// app 节点是需要插入的
			// app 节点对应的 fiber 是 hostRootFiber.child
			fiber.flags |= Placement;
		}
		// 这个fiber 是返回回去的子fiber
		return fiber;
	}

	// 多节点diff 步骤 1.将之前节点保存在map中
	// 2. 遍历 新的 jsx生成 的React 元素数组 判断是否可以复用  最后返回fiber 不能复用就创建新的fiber返回
	// 3. 返回fiber 之后 根据索引判断 当前这个fiber 标记移动还是插入还是不动
	// 4.将Map中剩下的标记为删除
	function reconcileChildrenArray(
		returnFiber: FiberNode,
		currentFirstChild: FiberNode | null,
		newChild: any[]
	) {
		// currentFirstChild 是一个 FiberNode 链表
		// newChild 是一个数组 jsx 编译的 里面 各种类型 text reactElement 都有  也可能是 数组 fragment

		// lastPlacedIndex 是一个 基准值：表示上一个处理完后 不用标记移动的fiber在老列表中对应的索引 默认值是0
		let lastPlacedIndex = 0;
		// 创建的最后一个fiber 可以是 复用的最好一个Fiber 就是 updateFromMap 函数返回的
		let lastNewFiber: FiberNode | null = null;
		// 创建的第一个fiber 和上面同一个意思
		let firstNewFiber: FiberNode | null = null;

		// 1.将current保存在map中
		const existingChildren: ExistingChildren = new Map();
		let current = currentFirstChild;
		// 将 currentFirstChild 所有 兄弟 保存到 map 中
		// existingChildren map 的作用就是 方便 找到newChild 里面 对应的 fiber节点 然后再判断这个  fiber 可不可以复用
		// 不可以副用的 都添加returnFiber.deletions 中 然后 returnFiber.flags 标记ChildDeletion 表示子节点有需要 删除的
		while (current !== null) {
			const keyToUse = current.key !== null ? current.key : current.index;
			// keyToUse 属性名 有key 则用key 没有则用 索引位置作为Key
			existingChildren.set(keyToUse, current);
			current = current.sibling;
		}

		for (let i = 0; i < newChild.length; i++) {
			// 2.遍历newChild，寻找是否可复用
			const after = newChild[i]; // reactElement 或者 Text 也可能是 数组
			// updateFromMap 会返回一个newFiber  newFiber 可能是 新建的 可能是复用的
			const newFiber = updateFromMap(returnFiber, existingChildren, i, after);

			// newChild[i] 是 null false 就会走这里
			if (newFiber === null) {
				// 返回null都表示 当前newChild[i] 没有对应的 类型
				continue;
			}

			// 3. 标记当前新的fiber移动还是插入还是 不操作

			newFiber.index = i; // 更新新Fiber 的实际位置
			newFiber.return = returnFiber; // 链接 return 因为可能是新的fiber

			if (lastNewFiber === null) {
				// 表示是 第一个
				lastNewFiber = newFiber;
				firstNewFiber = newFiber;
			} else {
				// 将更新后的 新建的fiber 或 复用的 fiber 按照 新列表的顺序连接起来   这里已经将Fiber 排序好了
				// 因为 fiber 已经是按照 更新后顺序排好了的 所以只需要 那些 复用的 fiber 要移动的 只需要标记 移动就行了 不用记录 位置信息

				// 在commit流程中 因为此时的 Fiber 都是按照 newChild 数组的顺序 连起来排好序的 所以不用管插到哪 插到不用移动的第一个兄弟节点前就行
				// 标记有Placement 的fiber节点 找他的 位标记的兄弟节点B就行了 执行hostParent.insertBefore(A, B) A表示当前需要移动的Fiber B A插入到B之前
				// 如果没找到B 那就直接 执行	parent.appendChild(A); 插入第一个就行了
				// fiber是按照更新后的ui排序好的 只是标记了Placement 的fiber对应的 dom 需要按照这个顺序移动 插入
				lastNewFiber.sibling = newFiber;
				// 更新最后Fiber
				lastNewFiber = lastNewFiber.sibling;
			}

			if (!shouldTrackEffects) {
				// 如果是不需要标记复作用的话 直接返回
				// 首先渲染 阶段 多节点的话 也都不标记 在 completeWork 阶段 会 统一生成离屏dom树
				continue;
			}

			// A1 B2 C3 D4 -> B2 C3 A1 D4
			// 0__1__2___3___0__1__2____3

			//  因为遍历的是  b c a d

			// lastPlacedIndex 是一个 基准值：表示上一个处理完后 不用标记移动的fiber在老列表中对应的索引 默认值是0
			// 开始的时候 是以第一个 可复用的FIber的 oldIndex 为基准 赋值给lastPlacedIndex
			// 然后判断下一个 fiber 是否可复用 不可复用就直接标记 Placement 插入
			// 如果是可复用的话 因为 此时更新后 当前element 是比上一个可复用的fiber对应的element 是靠右的 所以如果 只需要📒 当前element 对应的复用的Fiber的oldIndex 是不是小于
			// 是不是小于lastPlacedIndex 小于的话 标记该fiber 为 Placement移动 。如果是大于的话 因为在老列表中 当前可复用Fiber 位置 本来就是 在lastPlacedIndex 的右边 更新后还是在右边 那就不用移动
			// 此时 就更新astPlacedIndex 为 当前可复用 fiber的 oldIndex 就行了 然后在和后面的对比。
			// 标记完了fiber的 Placement 在commit流程中 因为此时的 Fiber 都是按照 newChild 数组的顺序 连起来排好序的 所以不用管插到哪 插到不用移动的第一个兄弟节点前就行
			// 标记有Placement 的fiber节点 找他的 位标记的兄弟节点B就行了 执行hostParent.insertBefore(A, B) A表示当前需要移动的Fiber B A插入到B之前
			// 如果没找到B 那就直接 执行	parent.appendChild(A); 插入第一个就行了
			// fiber是按照更新后的ui排序好的 只是标记了Placement 表达该fiber对应的 dom 需要按照这个顺序移动 插入

			// newFiber.alternate 不存在 说明是新建的
			const current = newFiber.alternate;
			if (current !== null) {
				// 说明当前是复用的
				const oldIndex = current.index; // 之前fiber 所在的位置
				if (oldIndex < lastPlacedIndex) {
					// 如果之前的位置小于 小于最后一个可复用fiber 在之前列表的位置 就移动
					// 移动
					newFiber.flags |= Placement;
					continue;
				} else {
					// 不移动 更新 lastPlacedIndex 的值
					lastPlacedIndex = oldIndex;
				}
				// 这样好理解一点
				// if (oldIndex >= lastPlacedIndex) {
				// 	lastPlacedIndex = oldIndex;
				// } else {
				// 	newFiber.flags |= Placement;
				// }
			} else {
				// 新建的 表示之前没有对应的fiber 标记为 插入 创建 就行
				// mount
				newFiber.flags |= Placement;
			}
		}
		// 4. 将Map中剩下的标记为删除
		existingChildren.forEach((fiber) => {
			deleteChild(returnFiber, fiber);
		});
		// 返回列表中的第一个Fiber 作为子Fiber
		return firstNewFiber;
	}

	function getElementKeyToUse(element: any, index?: number): Key {
		if (
			Array.isArray(element) ||
			typeof element === 'string' ||
			typeof element === 'number' ||
			element === undefined ||
			element === null
		) {
			return index;
		}
		return element.key !== null ? element.key : index;
	}

	function updateFromMap(
		returnFiber: FiberNode,
		existingChildren: ExistingChildren,
		index: number,
		element: any // element 可能是文本 数字 react元素 数组 null fragment 等
	): FiberNode | null {
		const keyToUse = getElementKeyToUse(element, index); // 新reactElement 元素的key 有key则用key 没有用 位置锁盈  替代
		const before = existingChildren.get(keyToUse); // key相同的 Fiber 表示 之前有 对应的节点

		// 然后判断  该fiber 可不可以复用

		// 当前 处理的element 是 HostText
		if (typeof element === 'string' || typeof element === 'number') {
			// 当前是 一个HostText 类型
			if (before) {
				// before 存在表示 说明key 已经相同了
				if (before.tag === HostText) {
					// 现在是文本 之前也是文本
					// 之前的 也是 HostText 类型 说明可以复用
					// 从map 中移除
					existingChildren.delete(keyToUse);
					return useFiber(before, { content: element + '' });
				}
			}
			// 不存在的话 或者 类型不同 表示是新增 或者dom类型改变了  直接新建一个 Fiber
			// 这里不用单独标记删除了 后面第4步统一标记existingChildren 剩余的 节点 删除
			return new FiberNode(HostText, { content: element + '' }, null);
		}

		// ReactElement
		if (typeof element === 'object' && element !== null) {
			switch (element.$$typeof) {
				case REACT_ELEMENT_TYPE:
					// 表示是 一个JSx react元素

					// 表示当前 react元素 是一个 fragment 类型的 react元素
					if (element.type === REACT_FRAGMENT_TYPE) {
						// 表示 是fragment
						// 返回一个 fragment 类型的Fiber 可能是 复用的 也可能是 新创建的
						return updateFragment(
							returnFiber,
							before,
							element,
							keyToUse,
							existingChildren
						);
					}

					// react元素
					if (before) {
						// 存在 则 key相同 之前有对应Fiber
						// type 相同 都div 都是 h1 这些
						if (before.type === element.type) {
							// 可以复用
							existingChildren.delete(keyToUse);
							// 返回复用的Fiber
							return useFiber(before, element.props);
						}
					}
					// 创建一个新的 react元素类型的 Fiber 根据不同 type 创建 不同的 tag
					// 可能新的element是一个 函数组件 div 都有可能
					return createFiberFromElement(element);
			}
		}

		// 处理element 是数组情况
		// 情况一：
		// arr = [<li>c</li>, <li>d</li>]
		//   <ul>
		//   <li>a</li>
		//   <li>b</li>
		//   {arr}
		// </ul>
		// jsxs('ul', {
		//   children: [
		//     jsx('li', {
		//       children: 'a'
		//     }),
		//     jsx('li', {
		//       children: 'b'
		//     }),
		//     arr
		//   ]
		// });
		// arr 就是 element
		if (Array.isArray(element)) {
			// 把数组 当做 一个 fragment 处理
			return updateFragment(
				returnFiber,
				before,
				element,
				keyToUse,
				existingChildren
			);
		}

		// 其他情况 return null
		return null;
	}

	return function reconcileChildFibers(
		returnFiber: FiberNode, //当前fiber
		currentFiber: FiberNode | null, // 子fiber 也就是当前对比的fiber  普通节点的首次渲染为null
		newChild?: any
	) {
		// 判断Fragment
		const isUnkeyedTopLevelFragment =
			typeof newChild === 'object' &&
			newChild !== null &&
			newChild.type === REACT_FRAGMENT_TYPE &&
			newChild.key === null;
		if (isUnkeyedTopLevelFragment) {
			// newChild是一个 fragment type类型的 react元素且没有key
			// <div>
			// <>
			// 	<div></div>
			// 	<div></div>
			// </>;
			// </div>
			// newChild = jsxs(Fragment, props:{
			//   children: [
			//       jsx("div", {}),
			//       jsx("div", {})
			//   ]
			// })
			// 处理的是这种顶级 fragment的情况 当前 newChild  是 一个type 类型为 fragment的 react元素
			// 这样取 他的 newChild.props.children 去生成对应的 子Fiber
			// 此时传入的 newChild 是一个 type 类型为 fragment的 react元素
			// 因为 returnFiber是一个div是 他子元素是一个 fragment 所以 newChild 就是一个类型为 fragment的 react元素
			// 这样就直接跳过 fragment 不为他创建 fragment 类型Fiber 因为没有key 没必要创建
			newChild = newChild.props.children;
		}

		// 判断当前fiber的类型
		// 是 一个 数组 或者 对象
		if (typeof newChild === 'object' && newChild !== null) {
			// 多节点的情况 ul> li*3
			// 数组的情况
			if (Array.isArray(newChild)) {
				// 如果是数组的话返回一个 子Fiber链表
				return reconcileChildrenArray(returnFiber, currentFiber, newChild);
			}

			switch (newChild.$$typeof) {
				// 到这表示 是单个节点的情况 或者是单个 < key = '1></> 有key的Fragment
				case REACT_ELEMENT_TYPE:
					// 表示newChild 是一个 reactElement 元素
					return placeSingleChild(
						// 返回一个 子fiber
						reconcileSingleElement(returnFiber, currentFiber, newChild)
					);
				default:
					if (true) {
						console.warn('未实现的reconcile类型', newChild);
					}
					break;
			}
		}

		// HostText 子dom 是 文本
		if (typeof newChild === 'string' || typeof newChild === 'number') {
			return placeSingleChild(
				reconcileSingleTextNode(returnFiber, currentFiber, newChild)
			);
		}

		if (currentFiber !== null) {
			// 兜底删除
			deleteRemainingChildren(returnFiber, currentFiber);
		}

		if (true) {
			console.warn('未实现的reconcile类型', newChild);
		}
		return null;
	};
}

function useFiber(fiber: FiberNode, pendingProps: Props): FiberNode {
	// 副用的话 就是 两个 fiber 来回复用 然后 重新设置一下 pendingProps 属性 和更新一下 其他属性就行了
	// 取缓冲区的 fiber 不是新建一个fiber
	// 如果之前没有 就新建 然后 alternate 互相指向
	const clone = createWorkInProgress(fiber, pendingProps);
	clone.index = 0;
	clone.sibling = null;
	return clone;
}

function updateFragment(
	returnFiber: FiberNode,
	current: FiberNode | undefined,
	elements: any,
	key: Key,
	existingChildren: ExistingChildren
) {
	let fiber;
	if (!current || current.tag !== Fragment) {
		// 之前的 Fiber 不存在 ，或者是 之前的不是 Fragment 类型的Fiber 不能复用
		// 新建一个Fiber 他的 pendingProps 就是 elements 然后创建该Fiber 的子Fiber 是 拿elements.props.children 当作newChild去创建
		fiber = createFiberFromFragment(elements, key);
	} else {
		existingChildren.delete(key);
		fiber = useFiber(current, elements);
	}
	fiber.return = returnFiber;
	return fiber;
}

// 副作用： 移动 插入 删除 创建 这些操作
// 根据 是否是 首次渲染和 更新 来决定是否 追踪副作用
// 这里有个优化
// 因为首次渲染的时候 对于fiber.tag 为 原生dom 这种类型的节点来说  如果子节点 每个都追踪副作用 就没每个都插入一次
// 所以不用记录子dom的 副作用 只要创建好一个dom树只之后再插入就行
export const reconcileChildFibers = ChildReconciler(true); // 追踪副作用呢

export const mountChildFibers = ChildReconciler(false); // 不追踪副作用

export function cloneChildFibers(wip: FiberNode) {
	// child  sibling
	if (wip.child === null) {
		return;
	}
	let currentChild = wip.child;
	let newChild = createWorkInProgress(currentChild, currentChild.pendingProps);
	wip.child = newChild;
	newChild.return = wip;

	while (currentChild.sibling !== null) {
		currentChild = currentChild.sibling;
		newChild = newChild.sibling = createWorkInProgress(
			newChild,
			newChild.pendingProps
		);
		newChild.return = wip;
	}
}
